local Class = require('lua.lang.Class')
local Constants = require('lua.vm.compiler.Constants')
local Lua = require('lua.vm.Lua')
local StringBuilder = require('lua.lang.StringBuilder')

--- @class Print : Class
--- @field ps PrintStream
local Print = Class {}
--- @type string[]
Print.hexArray = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }
Print.BRANCH = 2
Print.ERR_OP_LOADKX_NOT_OP_EXTRAARG = 0x7ffffffe
Print.ERR_OP_LOADKX_NO_NEXT = 2147483647
Print.HEADER = ', --[=========[ Lua assembler file generated by Lua-L '
Print.NEED_LABEL = 32
Print.NONE = 0
Print.NOP = Constants:createABC(Lua.OP_RETURN, 0, 1, 0)
Print.STRING_FOR_NULL = '\34\34 , null'
--- @type string[]
Print.OPNAMES = {
	'MOVE',
	'LOADK',
	'LOADKX',
	'LOADBOOL',
	'LOADNIL',
	'GETUPVAL',
	'GETTABUP',
	'GETTABLE',
	'SETTABUP',
	'SETUPVAL',
	'SETTABLE',
	'NEWTABLE',
	'SELF',
	'ADD',
	'SUB',
	'MUL',
	'DIV',
	'MOD',
	'POW',
	'UNM',
	'NOT',
	'LEN',
	'CONCAT',
	'JMP',
	'EQ',
	'LT',
	'LE',
	'TEST',
	'TESTSET',
	'CALL',
	'TAILCALL',
	'RETURN',
	'FORLOOP',
	'FORPREP',
	'TFORCALL',
	'TFORLOOP',
	'SETLIST',
	'CLOSURE',
	'VARARG',
	'EXTRAARG',
	'IDIV',
	'BNOT',
	'BAND',
	'BOR',
	'BXOR',
	'SHL',
	'SHR',
}
Print.TARGET = 64
Print.UNUSED = 3
Print.USED = 1
Print.USED_MASK = 31

--- @param sb StringBuilder
--- @param s string
--- @return StringBuilder
function Print:addCommon(sb, s)
	if sb == nil then
		sb = StringBuilder:new()
	end
	return sb:append('; '):append(s)
end

--- @param sb StringBuilder
--- @param maxUp number
--- @param n number
--- @return StringBuilder
function Print:checkUpvalue(sb, maxUp, n)
	if n >= maxUp then
		self:addCommon(sb, 'upvalue u' .. tostring(n) ..
				' out of upvalues list (' .. tostring(maxUp) ..
				' upvalues for this func)')
	end
	return sb
end

--- @param sb StringBuilder
--- @param max number
--- @param n number
--- @return StringBuilder
function Print:checkVariable(sb, max, n)
	if n < 0 then
		return self:addCommon(sb, 'impossible (negative) variable v' .. tostring(n))
	end
	if n >= max then
		return self:addCommon(sb, 'variable v' .. tostring(n) ..
				' out of stack (.maxstacksize = ' .. tostring(max) ..
				' for this func)')
	end
	return sb
end

--- @param f Prototype
--- @param fixed number[]
--- @return number[]
function Print:fixPrototype(f, fixed)
	fixed = fixed or {}
	local code = f.code
	local n = #code
	local used = {}
	local iteration = 0
	while iteration < 100 and self:fix(used, f, code, fixed) do
		if iteration == 0 then
			code = {}
		end
		for i = 1, n do
			code[i] = fixed[i]
		end
		for i = 1, n do
			used[i] = used[i] and -32
		end
		iteration = iteration + 1
	end
	return used
end

--- @param used number[]
--- @param f Prototype
--- @param code number[]
--- @param fixed number[]
--- @return boolean
function Print:fix(used, f, code, fixed)
	local changed = false
	local n = #code
	local maxOp = #self.OPNAMES - 1
	local maxConst = #f.k
	local maxClosure = #f.p
	local maxUpvalues = #f.upvalues
	local maxVars = f.maxStackSize
	local entry = self.NOP
	local pc, u, i, origPc, o, a, b, c, bx, sbx, mode, to, bMode,
	cMode, i2, i3, i4, pc2, i5, o2, out, prev, sbx2, to2, a2
	:: L3 ::
	pc = entry
	:: L4 ::
	if pc < 0 then
		goto L7
	end
	if pc >= n then
		goto L7
	end
	u = used[pc] & self.USED_MASK
	if u == 1 then
		goto L7
	end
	used[pc] = used[pc] + (1 - u)
	i = code[pc]
	origPc = pc
	o = Lua:getOpCode(i)
	if o < maxOp then
		goto L24
	end
	i = self.NOP
	:: L21 ::
	if i == i then
		goto L23
	end
	changed = true
	:: L23 ::
	fixed[origPc] = i
	pc = pc + 1
	goto L4
	:: L24 ::
	a = Lua:getArgA(i)
	b = Lua:getArgB(i)
	c = Lua:getArgC(i)
	bx = Lua:getArgBx(i)
	sbx = Lua:getArgSbx(i)
	mode = Lua:getOpMode(o)
	if mode == Lua.iABC then
		goto L28
	elseif mode == Lua.iABx then
		goto L120
	elseif mode == Lua.iAsBx then
		goto L136
	elseif mode == Lua.iAx then
		goto L157
	else
		goto L21
	end
	:: L120 ::
	if o == Lua.OP_LOADK then
		goto L122
	elseif o == Lua.OP_LOADKX then
		goto L125
	elseif o == Lua.OP_CLOSURE then
		goto L133
	else
		goto L21
	end
	:: L122 ::
	if a >= maxVars then
		goto L124
	end
	if bx < maxConst then
		goto L21
	end
	:: L124 ::
	i = self.NOP
	goto L21
	:: L125 ::
	if a < maxVars then
		goto L127
	end
	:: L132 ::
	i = self.NOP
	goto L21
	:: L127 ::
	if pc == (n - 1) then
		goto L132
	end
	if Lua:getOpCode(code[pc + 1]) ~= Lua.OP_EXTRAARG then
		goto L132
	end
	if Lua:getArgAx(code[pc + 1]) < maxConst then
		goto L21
	end
	:: L133 ::
	if a >= maxVars then
		goto L135
	end
	if bx < maxClosure then
		goto L21
	end
	:: L135 ::
	i = self.NOP
	goto L21
	:: L136 ::
	if o == Lua.OP_FORLOOP then
		goto L146
	elseif o == Lua.OP_FORPREP then
		goto L146
	elseif o == Lua.OP_TFORCALL then
		goto L137
	elseif o == Lua.OP_TFORLOOP then
		goto L143
	else
		goto L137
	end
	:: L137 ::
	to = (sbx + pc) + 1
	if to >= 0 then
		goto L140
	end
	:: L141 ::
	i = self.NOP
	goto L21
	:: L140 ::
	if to >= n then
		goto L141
	end
	if i ~= self.NOP then
		goto L151
	end
	:: L153 ::
	used[to] = used[to] | self.NEED_LABEL
	goto L21
	:: L151 ::
	if o ~= Lua.OP_JMP then
		goto L155
	end
	pc = to - 1
	goto L153
	:: L155 ::
	if (used[to] & self.USED_MASK) ~= 0 then
		goto L153
	end
	used[to] = (used[to] + self.BRANCH) | 64
	goto L153
	:: L143 ::
	if a < (maxVars - 1) then
		goto L137
	end
	i = self.NOP
	goto L137
	:: L146 ::
	if a < (maxVars - 2) then
		goto L137
	end
	i = self.NOP
	goto L137
	:: L157 ::
	if pc ~= 0 then
		goto L159
	end
	:: L162 ::
	i = self.NOP
	goto L21
	:: L159 ::
	if Lua:getOpCode(code[pc - 1]) ~= 2 then
		goto L162
	end
	if Lua:getArgAx(i) < maxConst then
		goto L21
	end
	:: L28 ::
	if o ~= Lua.OP_RETURN then
		goto L33
	end
	if b ~= 1 then
		goto L33
	end
	pc = entry - 1
	:: L33 ::
	if o ~= Lua.OP_EQ then
		goto L35
	end
	:: L42 ::
	if o == Lua.OP_LOADNIL then
		goto L72
	elseif o == Lua.OP_CALL then
		goto L74
	elseif o == Lua.OP_TAILCALL then
		goto L74
	elseif o == Lua.OP_RETURN then
		goto L74
	elseif o == Lua.OP_TFORCALL then
		goto L96
	elseif o == Lua.OP_SETLIST then
		goto L99
	elseif o == Lua.OP_VARARG then
		goto L74
	else
		goto L43
	end
	:: L43 ::
	bMode = Lua:getBMode(o)
	if bMode ~= 0 then
		goto L46
	end
	:: L52 ::
	cMode = Lua:getCMode(o)
	if cMode ~= 0 then
		goto L55
	end
	:: L61 ::
	if o == Lua.OP_LOADBOOL then
		goto L63
	elseif o == Lua.OP_EQ or o == Lua.OP_LT or o == Lua.OP_LE or
			o == Lua.OP_TEST or o == Lua.OP_TESTSET then
		goto L65
	else
		goto L21
	end
	:: L63 ::
	if c == 0 then
		goto L21
	end
	:: L65 ::
	if i == self.NOP then
		goto L21
	end
	if (used[pc + 2] & self.USED_MASK) ~= 0 then
		goto L21
	end
	used[pc + 2] = (used[pc + 2] + BRANCH) | 64
	goto L21
	:: L55 ::
	if cMode ~= 3 then
		goto L117
	end
	if Lua:isk(c) == false then
		goto L117
	end
	if Lua:indexK(c) < maxConst then
		goto L61
	end
	i = self.NOP
	:: L117 ::
	if cMode == 1 then
		goto L61
	end
	if c < maxVars then
		goto L61
	end
	i = self.NOP
	goto L61
	:: L46 ::
	if bMode ~= 3 then
		goto L107
	end
	if Lua:isk(b) == false then
		goto L107
	end
	if Lua:indexK(b) < maxConst then
		goto L52
	end
	i = self.NOP
	:: L107 ::
	if o == Lua.OP_LOADBOOL then
		goto L52
	end
	if o == Lua.OP_NEWTABLE then
		goto L52
	end
	if bMode ~= 1 then
		goto L114
	end
	if b < maxUpvalues then
		goto L52
	end
	i = self.NOP
	goto L52
	:: L114 ::
	if b < maxVars then
		goto L52
	end
	i = self.NOP
	goto L52
	:: L74 ::
	if b <= 0 then
		goto L82
	end
	i2 = a + b
	if o ~= Lua.OP_RETURN then
		goto L94
	end
	i3 = 2
	:: L79 ::
	if (i2 - i3) < maxVars then
		goto L82
	end
	i = self.NOP
	goto L82
	:: L94 ::
	i3 = 1
	:: L82 ::
	if o ~= Lua.OP_RETURN then
		goto L84
	end
	:: L85 ::
	pc = entry - 1
	:: L87 ::
	if o ~= Lua.OP_CALL then
		goto L21
	end
	if c <= 1 then
		goto L21
	end
	if a < maxVars then
		goto L92
	end
	:: L93 ::
	i = self.NOP
	goto L21
	:: L92 ::
	if ((a + c) - 2) < maxVars then
		goto L21
	end
	:: L84 ::
	if o ~= Lua.OP_TAILCALL then
		goto L87
	end
	:: L72 ::
	if (a + b) < maxVars then
		goto L21
	end
	i = self.NOP
	goto L21
	:: L96 ::
	if ((a + c) + 2) < maxVars then
		goto L21
	end
	i = self.NOP
	goto L21
	:: L99 ::
	if (a + b) < maxVars then
		goto L101
	end
	i = self.NOP
	goto L21
	:: L101 ::
	if c ~= 0 then
		goto L21
	end
	if pc ~= (n - 1) then
		goto L105
	end
	i = self.NOP
	goto L21
	:: L105 ::
	pc = pc + 1
	used[pc] = (used[pc] & (-32)) + 1
	goto L21
	:: L35 ::
	if o == Lua.OP_LT or o == Lua.OP_LE then
		goto L42
	end
	if o ~= Lua.OP_SETTABUP then
		goto L69
	end
	if a < maxUpvalues then
		goto L42
	end
	i = self.NOP
	goto L42
	:: L69 ::
	if a < maxVars then
		goto L42
	end
	i = self.NOP
	:: L7 ::
	entry = -1
	i4 = self.NOP
	:: L9 ::
	if i4 >= n then
		goto L10
	end
	if (used[i4] & self.USED_MASK) == 2 then
		goto L165
	end
	i4 = i4 + 1
	goto L9
	:: L165 ::
	entry = i4
	:: L10 ::
	if entry > 0 then
		goto L3
	end
	pc2 = self.NOP
	:: L13 ::
	if pc2 >= n then
		goto L14
	end
	if (used[pc2] & self.USED_MASK) ~= 0 then
		goto L182
	end
	i5 = code[pc2]
	o2 = Lua:getOpCode(i5)
	if o2 == Lua.OP_JMP then
		goto L194
	elseif o2 == Lua.OP_RETURN then
		goto L184
	elseif o2 == Lua.OP_FORLOOP then
		goto L194
	elseif o2 == Lua.OP_FORPREP then
		goto L194
	elseif o2 == Lua.OP_TFORLOOP then
		goto L194
	else
		goto L171
	end
	:: L171 ::
	i5 = self.NOP
	:: L173 ::
	if i5 == self.NOP then
		goto L175
	end
	:: L176 ::
	used[pc2] = used[pc2] + self.UNUSED
	:: L177 ::
	if changed == false then
		goto L179
	end
	:: L181 ::
	fixed[pc2] = i5
	goto L182
	:: L179 ::
	if i5 == code[pc2] then
		goto L181
	end
	changed = true
	goto L181
	:: L175 ::
	if pc2 ~= (n - 1) then
		goto L177
	end
	:: L194 ::
	sbx2 = Lua:getArgSbx(i5)
	to2 = (sbx2 + pc2) + 1
	if to2 >= 0 then
		goto L197
	end
	:: L209 ::
	out = true
	:: L199 ::
	if out == true then
		goto L202
	end
	used[to2] = used[to2] | 32
	:: L202 ::
	if o2 ~= Lua.OP_JMP then
		goto L171
	end
	if out == false then
		goto L205
	end
	:: L208 ::
	i5 = self.NOP
	goto L173
	:: L205 ::
	if (used[to2] & 31) == 0 then
		goto L208
	end
	if (used[pc2 - 1] & 31) == 0 then
		goto L208
	end
	prev = Lua:getOpCode(code[pc2 - 1])
	if prev ~= Lua.OP_JMP then
		goto L213
	end
	:: L214 ::
	i5 = self.NOP
	goto L173
	:: L213 ::
	if prev ~= Lua.OP_RETURN then
		goto L173
	end
	:: L197 ::
	if to2 >= n then
		goto L209
	end
	out = false
	goto L199
	:: L184 ::
	if pc2 ~= (n - 1) then
		goto L186
	end
	i5 = self.NOP
	goto L173
	:: L186 ::
	a2 = Lua:getArgA(i5)
	if Lua:getArgB(i5) < 0 then
		goto L193
	end
	if a2 >= maxVars then
		goto L193
	end
	if ((a2 + r3) - 2) >= maxVars then
		goto L193
	end
	if (used[pc2 - 1] & 31) ~= 0 then
		goto L173
	end
	:: L193 ::
	i5 = self.NOP
	:: L182 ::
	pc2 = pc2 + 1
	goto L13
	:: L14 ::
	return changed
end

--- @param s string
--- @param maxCols number
--- @return void
function Print:format(s, maxCols)
	local n = s:len()
	local ps = self.ps
	if n > maxCols then
		ps:printString(s:sub(1, maxCols))
		return
	end
	ps:printString(s)
	local i = maxCols - n
	while true do
		i = i - 1
		if i >= 0 then
			ps:printChar(' ')
		else
			return
		end
	end
end

return Print